前言
最近從 JavaScript Promises … In Wicked Detail 研究Promise的實作原理,在此紀錄一下。
這邊的Promise會盡量符合Promises/A+
的規範但離完整的實作仍會有些細節上處理的差距。
基礎
假設我們希望將callback改成then的串鏈形式,我們只需要簡單改變結構。
1 2 3 4 5 6 7 8
| doSomething(function(value) { console.log('Got a value:' + value); }); function doSomething(callback) { var value = 42; callback(value); }
|
改變成
1 2 3 4 5 6 7 8 9 10 11 12
| doSomething().then(function(value) { console.log('Got a value:' + value); }); function doSomething() { return { then: function(callback) { var value = 42; callback(value); } }; }
|
Promise
接下來開始實作Promise物件,使他適用到任何函式中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function Promise(fn) { var callback = null; this.then = function(cb) { callback = cb; }; function resolve(value) { callback(value); } fn(resolve); } var promise = new Promise(function(resolve) { var value = 42; resolve(value); }) ; promise.then(function(value) { console.log('Got a value:' + value); });
|
這樣子的結構會有個問題,因為resolve會在then之前執行,所以callback目前的狀態還是null。
在這邊利用setTimeout讓resolve跳出目前的事件流,讓then在resolve前執行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function Promise(fn) { var callback = null; this.then = function(cb) { callback = cb; }; function resolve(value) { setTimeout(function(){ callback(value); },0) } fn(resolve); } var promise = new Promise(function(resolve) { var value = 42; resolve(value); }) ; promise.then(function(value) { console.log('Got a value:' + value); });
|
乍看之下好像完成了,但是一旦then是非同步執行,這樣的結構就會出問題。
因為如此一來resolve一樣在then之前執行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function Promise(fn) { var callback = null; this.then = function(cb) { callback = cb; }; function resolve(value) { setTimeout(function(){ callback(value); },0) } fn(resolve); } var promise = new Promise(function(resolve) { var value = 42; resolve(value); }) ; setTimeout(function(){ promise.then(function(value) { console.log('Got a value:' + value); }); },100)
|
狀態機
可以將狀態
及結果
記錄在Promise中,利用當前狀態判斷執行順序再將結果傳回欲執行的函式。
若then在resolve前執行則先暫時將then的函式存在deferred中,等候resolve完才執行。
反之若resolve在then前執行,則因為狀態已改變為resolved,可直接接著執行then函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| function Promise(fn) { var state = 'pending'; var value; var deferred; function resolve(newValue) { value = newValue; state = 'resolved'; if(deferred) { handle(deferred); } } function handle(onResolved) { if(state === 'pending') { deferred = onResolved; return; } onResolved(value); } this.then = function(onResolved) { handle(onResolved); }; fn(resolve); } var promise = new Promise(function(resolve) { setTimeout(function(){ var value = 42; resolve(value); },500); }) ; promise.then(function(value) { console.log('1. execute then before resolve , value :' + value); }); promise.then(function(value) { console.log('2. execute then before resolve , value :' + value); }); setTimeout(function(){ promise.then(function(value) { console.log('3. execute then before resolve , value :' + value); }); },300); setTimeout(function(){ promise.then(function(value) { console.log('execute then after resolve , value :' + value); }); },1000);
|
可以發現如果有多個then在resolve前執行會只有最後一個then會執行,這是因為不斷將deferred覆蓋過去了,所以實作上會利用陣列來儲存函式。
串鏈Promises
若我們希望then之後可以接著then,如此一來我們必須再then中傳回Promise物件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| function Promise(fn) { var state = 'pending'; var value; var deferred = null; function resolve(newValue) { value = newValue; state = 'resolved'; if(deferred) { handle(deferred); } } function handle(handler) { if(state === 'pending') { deferred = handler; return; } if(!handler.onResolved) { handler.resolve(value); return; } var ret = handler.onResolved(value); handler.resolve(ret); } this.then = function(onResolved) { return new Promise(function(resolve) { handle({ onResolved: onResolved, resolve: resolve }); }); }; fn(resolve); } var promise = new Promise(function(resolve) { var value = 42; resolve(value); }).then(function(result) { console.log("First result:" + result); return result+1; }).then(function(result) { console.log("Second result:" + result); return result+1; }).then(function(result) { console.log("Third result:" + result); });
|
一般建立的Promise和then建立的Promise有些許的不同
在then建立的Promise中我們把原先的value傳進去執行then中的函式,再將執行結果傳給原先Promise的resolve。
1 2
| var ret = handler.onResolved(value); handler.resolve(ret);
|
文中給的then不一定要有callback函式,因此在handle函式做了判斷
1 2 3 4
| if(!handler.onResolved) { handler.resolve(value); return; }
|
不過我暫時想不到什麼情況下需要一個沒有callback的then。
串鏈中回傳Promise
then中的程式如果是非同步執行,如此一來必須在串鏈中回傳Promise。
1 2 3 4 5 6 7 8 9 10 11 12
| promise.then(function(result) { return new Promise(function(resolve){ setTimeout(function(){ console.log("First result:" + result); resolve(result+1); },500) }); }).then(function(anotherPromise) { anotherPromise.then(function(finalResult) { console.log("the final result is", finalResult); }); });
|
我們可以將判別是否為Promise的程式寫到resolve中,我們會不斷的遞迴呼叫resolve函式直到不再是Promise為止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| function Promise(fn) { var state = 'pending'; var value; var deferred = null; function resolve(newValue) { if(newValue && typeof newValue.then === 'function') { newValue.then(resolve); return; } state = 'resolved'; value = newValue; if(deferred) { handle(deferred); } } function handle(handler) { if(state === 'pending') { deferred = handler; return; } if(!handler.onResolved) { handler.resolve(value); return; } var ret = handler.onResolved(value); handler.resolve(ret); } this.then = function(onResolved) { return new Promise(function(resolve) { handle({ onResolved: onResolved, resolve: resolve }); }); }; fn(resolve); } var promise = new Promise(function(resolve) { var value = 42; resolve(value); }) promise.then(function(result) { return new Promise(function(resolve){ setTimeout(function(){ console.log("First result:" + result); resolve(result+1); },500) }); }).then(function(result) { console.log("Second result:" + result); return result+1; }).then(function(result) { console.log("Third result:" + result); });
|
其他
錯誤處理及一些實作上的細節等到之後補充。
參考
JavaScript Promises … In Wicked Detail